﻿using System;
using System.Xml.Serialization;
using System.Collections.Generic;
using System.Reflection;

namespace Plugins.Xml.Serialization.Internal
{
    internal class ObjectProperty :
        IObjectProperty,
        IObjectPropertyReader,
        IObjectPropertyWriter,
        IXmlWriter,
        IXmlReader
    {
        private PropertyInfo _property;
        private object _object;
        private Type _objectType;
        private bool _isReadable;
        private bool _isWriteable;
        private bool _skipSerialization;
        private XmlSerializer _serializer;
        private XmlRuntimeSerializer _runtimeSerializer;

        public IObjectPropertyReader Reader { get { return this; } }
        public IObjectPropertyWriter Writer { get { return this; } }

        public ObjectProperty(object o, Type objectType, PropertyInfo property)
        {
            _objectType = objectType;
            _object = o;
            _property = property;
            _skipSerialization = CanSkipSerialization();
            if (!_skipSerialization)
            {
                if (_property.GetGetMethod() != null)
                {
                    _isReadable = true;
                }
                if (_property.GetSetMethod() != null)
                {
                    _isWriteable = true;
                }
                XmlSerializerAttribute serializerAttribute = GetXmlSerializer();
                if (serializerAttribute == null)
                {
                    serializerAttribute = new XmlSerializerAttribute(_property.PropertyType);
                }
                if (serializerAttribute.Serializer != null)
                {
                    if (serializerAttribute.Serializer.RuntimeType == _property.PropertyType)
                    {
                        _serializer = serializerAttribute.Serializer;
                    }
                }
                XmlRuntimeSerializerAttribute runtimeSerializerAttribute = GetRuntimeXmlSerializer();
                if (runtimeSerializerAttribute == null)
                {
                    runtimeSerializerAttribute = new XmlRuntimeSerializerAttribute(_property.PropertyType);
                }
                if (runtimeSerializerAttribute != null)
                {
                    if (runtimeSerializerAttribute.RuntimeSerializer != null)
                    {
                        if (runtimeSerializerAttribute.RuntimeSerializer.RuntimeType == _property.PropertyType)
                        {
                            _runtimeSerializer = runtimeSerializerAttribute.RuntimeSerializer;
                        }
                    }
                }
            }
        }

        private XmlSerializerAttribute GetXmlSerializer()
        {
            List<XmlSerializerAttribute> result = new List<XmlSerializerAttribute>();
            XmlSerializerAttribute[] attributes = (XmlSerializerAttribute[])_property.GetCustomAttributes(typeof(XmlSerializerAttribute), true);
            if (attributes.Length > 0) return attributes[0];
            return null;
        }

        private XmlRuntimeSerializerAttribute GetRuntimeXmlSerializer()
        {
            List<XmlRuntimeSerializerAttribute> result = new List<XmlRuntimeSerializerAttribute>();
            XmlRuntimeSerializerAttribute[] attributes = (XmlRuntimeSerializerAttribute[])_property.GetCustomAttributes(typeof(XmlRuntimeSerializerAttribute), true);
            if (attributes.Length > 0) return attributes[0];
            return null;
        }

        private bool CanSkipSerialization()
        {
            List<XmlRuntimeSerializerAttribute> result = new List<XmlRuntimeSerializerAttribute>();
            XmlIgnoreAttribute[] attributes = (XmlIgnoreAttribute[])_property.GetCustomAttributes(typeof(XmlIgnoreAttribute), true);
            return attributes.Length > 0;
        }

        public XmlSerializer Serializer { get { return _serializer; } }
        public XmlRuntimeSerializer RuntimeSerializer { get { return _runtimeSerializer; } }

        public bool NonSerializable { get { return _skipSerialization; } }
        public bool SupportsSerialization { get { return _serializer != null; } }
        public bool SupportsRuntimeSerialization { get { return _runtimeSerializer != null; } }

        public override string ToString()
        {
            return _property.Name;
        }

        public string Read()
        {
            if (!_skipSerialization && _serializer != null && _isReadable)
            {
                return _serializer.Serialize(_property.GetValue(_object, null));
            }
            return null;
        }

        public void Write(string value)
        {
            if (!_skipSerialization && _serializer != null && _isWriteable)
            {
                _property.SetValue(_object, _serializer.Deserialize(value), null);
            }
        }

        void IXmlWriter.Write(IXmlPropertyWriter node, string propertyName)
        {
            if (!_skipSerialization && _runtimeSerializer != null && _isReadable)
            {
                _runtimeSerializer.Serialize(_property.GetValue(_object, null), node, propertyName);
            }
        }

        void IXmlWriter.Write(IXmlPropertyWriter node, int index)
        {
            if (!_skipSerialization && _runtimeSerializer != null && _isReadable)
            {
                _runtimeSerializer.Serialize(_property.GetValue(_object, null), node, index);
            }
        }

        void IXmlReader.Read(IXmlPropertyReader node, string propertyName)
        {
            if (!_skipSerialization && _runtimeSerializer != null && _isWriteable)
            {
                _property.SetValue(_object, _runtimeSerializer.Deserialize(node, propertyName), null);
            }
        }
        void IXmlReader.Read(IXmlPropertyReader node, int index)
        {
            if (!_skipSerialization && _runtimeSerializer != null && _isWriteable)
            {
                _property.SetValue(_object, _runtimeSerializer.Deserialize(node, index), null);
            }
        }

        public string Name { get { return _property.Name; } }

        public object Object { get { return _object; } }
    }
}